/*
 * WidgetConfig.cpp
 *
 *  Created on: 2010-4-24
 *      Author: anod
 */

#include <cstdio>
#include <cerrno>
#include <new>

#include <QFile>
#include <QtXml/QDomDocument>

#include <IOEvent.hpp>
#include <Module-AppConfig/WidgetConfig.hpp>
#include <Module-AppConfig/WidgetConfigEvent.hpp>

static char except_tr[256];//用来抛异常用的字符串
static Event *event_obj;//用来发送协议的事件

namespace AppConfig
{

/**
 * @func(setInterval) 设置程序摘取网页数据的时间间隔
 * @arg:
 *      参数t表示设定的时间间隔，单位是毫秒
 */
void WidgetConfig::setInterval(unsigned long t)
{
	unsigned long old_value = _grab_timegap;
	unsigned char stack_mem[sizeof(WidgetConfigEvent)];
	_grab_timegap = t;
	if( old_value != _grab_timegap )
	{
		event_obj = new(stack_mem) WidgetConfigEvent(
			WidgetConfigEvent::VALUE_DIRTY,
			WidgetConfigEvent::WCE_INTERVAL_CODE,
			QString("%1").arg(old_value),
			QString("%1").arg(_grab_timegap)
		);
		eventHappen(*event_obj);
		event_obj->~Event();
	}
}

/**
 * @func(getInterval) 获取程序摘取网页数据的时间间隔
 * @return 返回摘取网页数据的时间间隔
 */
unsigned long WidgetConfig::getInterval() const
{
	return _grab_timegap;
}

/**
 * @func(setURL) 设置程序获取天气数据所需要的网页的url
 * @arg:
 *      参数url就是保存有天气数据的网页url
 */
void WidgetConfig::setURL(const QString& url)
{
	QString old_value(_data_url);
	unsigned char stack_mem[sizeof(WidgetConfigEvent)];
	_data_url = url;
	if( old_value != _data_url )
	{
		event_obj = new(stack_mem) WidgetConfigEvent(
			WidgetConfigEvent::VALUE_DIRTY,
			WidgetConfigEvent::WCE_URL_CODE,
			old_value,
			url
		);
		eventHappen(*event_obj);
		event_obj->~Event();
	}
}

/**
 * @func(getURL) 获取设置好的天气数据页面的url
 * @return 返回一个url，可从这个网络资源获取到天气数据
 */
const QString& WidgetConfig::getURL() const
{
	return _data_url;
}

/**
 * @func(setScript) 设置用来分析抓回来的网页数据的脚本
 * @arg:
 *      参数code_filename是分析网页数据用的脚本的文件系统路径
 */
void WidgetConfig::setScript(const QString& code_filename)
{

	QString old_value(_script_file);
	unsigned char stack_mem[sizeof(WidgetConfigEvent)];
	_script_file = code_filename;
	if( old_value != _data_url )
	{
		event_obj = new(stack_mem) WidgetConfigEvent(
			WidgetConfigEvent::VALUE_DIRTY,
			WidgetConfigEvent::WCE_SCRIPT_CODE,
			old_value,
			code_filename
		);
		eventHappen(*event_obj);
		event_obj->~Event();
	}
}

/**
 * @func(getScript) 获取用来分析抓回来的网页数据的脚本
 * @return 返回用来分析抓回来的网页数据的脚本的系统路径
 */
const QString& WidgetConfig::getScript() const
{
	return _script_file;
}

#define POLICY_TYPE_MAX POLICY_TYPE_SHAREDLIB
/**
 * @func(setPolicy) 设置网页分析策略，分别是用子进程，脚本和动态库函数来执行
 * @arg:
 *      参数type对应于POLICY_TYPE_开头的三个常量，表示
 *      子进程：用户自己写了一个可执行文件来分析网页，程序将调用这个可执行文件进行网页分析
 *      脚本：用户自己按照《策略描述文档说明》里面的内容自己写了一个文档，程序将按照格式进行数据获取
 *      动态库：用户提供了一个函数来做分析，程序将调用这个函数来完成网页分析
 */
void WidgetConfig::setPolicy(unsigned char type) throw(std::invalid_argument)
{
	unsigned char stack_mem[sizeof(WidgetConfigEvent)];
	if (type <= POLICY_TYPE_MAX )
	{
		unsigned char old_value = _paser_policy;
		_paser_policy = type;
		if( old_value != _paser_policy )
		{
			event_obj = new(stack_mem) WidgetConfigEvent(
				WidgetConfigEvent::VALUE_DIRTY,
				WidgetConfigEvent::WCE_POLICY_CODE,
				QString("%1").arg(old_value),
				QString("%1").arg(_paser_policy)
			);
			eventHappen(*event_obj);
			event_obj->~Event();
		}
	}
	else
	{
		sprintf(except_tr, "method setPolicy only accept args int value at [0,%d]", POLICY_TYPE_MAX);
		throw std::invalid_argument(except_tr);
	}
}

static const char* policy_name_ar[] = {
	"script",
	"process",
	"shared lib"
};
/**
 * @func(setPolicy) 设置网页分析策略，分别是用子进程，脚本和动态库函数来执行
 * @arg:
 *      参数type_name只能定义为"script", "process"和"shared lib"三个字符串常量之一，否则会抛出异常
 */
void WidgetConfig::setPolicy(const char* type_name) throw(std::invalid_argument)
{
	int i;
	for( i=0; i <= POLICY_TYPE_MAX; ++i )
	{
		if( strcmp(type_name, policy_name_ar[i]) == 0 )
			break;
	}
	setPolicy(i);
}

/**
 * @func(getPolicy) 获取程序使用的网页分析方式
 * @return 返回值只有可能出现在POLICY_TYPE_开头的几个常量之一
 */
unsigned char WidgetConfig::getPolicy()
{
	return _paser_policy;
}

/**
 * @func(setSharedLibFunc) 如果使用动态库的某个函数做分析，则需要传入函数名
 * @arg:
 *      参数func_name是动态库使用的函数名称
 */
void WidgetConfig::setSharedLibFunc(const char* func_name) throw(std::length_error)
{
	unsigned char stack_mem[sizeof(WidgetConfigEvent)];
	if( strlen(func_name)<SHAREDLIB_FUNCNAME_MAXLEN )
	{
		QString old_value(_dll_funcname);
		strcpy(_dll_funcname, func_name);
		if( old_value!= _dll_funcname )
		{
			event_obj = new(stack_mem) WidgetConfigEvent(
				WidgetConfigEvent::VALUE_DIRTY,
				WidgetConfigEvent::WCE_SHLIB_CODE,
				QString("%1").arg(old_value),
				QString("%1").arg(_dll_funcname)
			);
			eventHappen(*event_obj);
			event_obj->~Event();
		}
	}
	else
	{
		sprintf(except_tr, "the length of function name in shared lib should less than %d", SHAREDLIB_FUNCNAME_MAXLEN);
		throw std::length_error(except_tr);
	}
}

/**
 * @func(getSharedLibFunc) 获得动态库的分析函数名
 * @return 返回动态库的分析函数名称
 */
const char* WidgetConfig::getSharedLibFunc() const
{
	return _dll_funcname;
}

// 各种构造函数的实现

/**
 * 默认构造函数，什么都是默认的
 */
WidgetConfig::WidgetConfig()
#define DEF_TIMEGAP 30*60*1000
#define DEF_WEBPAGE "http://weather.yahoo.com/china/guangdong/guangzhou-2161838?unit=c"
#define DEF_SCRIPT  ".kde/share/apps/plasma/plasmoids/kde_weather/.def_script"
#define DEF_POLICY  POLICY_TYPE_SCRIPT
:_grab_timegap(DEF_TIMEGAP),			//默认每30分钟抓一次数据
 _data_url(DEF_WEBPAGE),				//我使用的是雅虎天气广州的数据
 _script_file(DEF_SCRIPT),				//我自己写的脚本
 _paser_policy(DEF_POLICY)				//默认使用我写的脚本
{
	_dll_funcname[0] = '\0';			//默认为""
}

/**
 * 构造函数，从一个文件里面读取配置，不保证其完整性，所以可能抛异常
 * @file_name 就是文件名了
 */
WidgetConfig::WidgetConfig(const QString& file_name)
{
	//debug提示:我印象中好像在构造函数中调用某个sb方法后不会直接改变自己的值的，shit
	if( load(file_name)!=0 ) //Cao,失败了
	{
		throw std::logic_error("error occur when loading config, please check the file:"+file_name.toStdString());
	}
}

/**
 * 构造函数，所有是数据都传进来，不保证其完整性，所以可能抛异常
 * @timegap  时间间隔
 * @url      数据地址
 * @script   脚本文件名
 * @policy   策略类型
 * @function 动态库函数名，如果不用动态库就传""
 */
WidgetConfig::WidgetConfig(unsigned long timegap,
		                   const QString& url,
		                   const QString& script,
		                   unsigned char policy,
		                   const char* funcname)
{
	_dll_funcname[0] = '\0';

	setInterval(timegap);
	setURL(url);
	setScript(script);
	setPolicy(policy);
	setSharedLibFunc(funcname);
}

/**
 * @func(load) 读取配置文件，得到程序参数
 * @file_name 配置文件的文件名
 */
int WidgetConfig::load( const QString& file_name )
{
	unsigned char stack_mem[sizeof(IOEvent)];
	QDomDocument doc("WidgetConfig");

	// 打开文件
	QFile inf( file_name );
	bool handle_result;
	handle_result = inf.open( QIODevice::ReadOnly );
	if( !handle_result )//出现错误
	{
		event_obj = new(stack_mem) IOEvent(
			Event::IOEVENT_LOAD_FAILED,			//事件类型：保存失败
			file_name.toStdString().c_str(),	//设备名称：文件名
			errno,								//错误类型，可能会过时了
			inf.errorString().toStdString().c_str(),//错误描述
			NULL
		);
		eventHappen(*event_obj);
		event_obj->~Event();
		return -1;//表示操作失败
	}
	//载人到Dom
	handle_result = doc.setContent(&inf);
	if( !handle_result )//又出错，应该是xml不合适
	{
		event_obj = new(stack_mem) IOEvent(
			Event::IOEVENT_LOAD_FAILED,			//事件类型：保存失败
			file_name.toStdString().c_str(),	//设备名称：文件名
			0,									//错误类型，可能会过时了
			"source file parse failed",         //错误描述
			NULL
		);
		eventHappen(*event_obj);
		event_obj->~Event();
		inf.close();
		return -2;//表示操作失败
	}
	inf.close();

	// 时间间隔
	QDomNodeList nodes = doc.elementsByTagName("TimeGap");
	if( nodes.isEmpty() )//没有，出错了
	{
		event_obj = new(stack_mem) IOEvent(
			Event::IOEVENT_LOAD_FAILED,			//事件类型：保存失败
			file_name.toStdString().c_str(),	//设备名称：文件名
			0,									//错误类型，可能会过时了
			"cannot find tag " "TimeGap",       //错误描述
			doc.toString().toStdString().c_str()//xml的内容
		);
		eventHappen(*event_obj);
		event_obj->~Event();
		return -3;//表示操作失败
	}
	else
	{
		QDomElement elem = nodes.at(0).toElement();
		unsigned long t = elem.text().toULong(&handle_result);
		if( !handle_result )//不是数字，出错了
		{
			event_obj = new(stack_mem) IOEvent(
				Event::IOEVENT_LOAD_FAILED,			//事件类型：保存失败
				file_name.toStdString().c_str(),	//设备名称：文件名
				0,									//错误类型，可能会过时了
				"tag value error " "TimeGap",       //错误描述
				elem.text().toStdString().c_str()	//text的内容
			);
			eventHappen(*event_obj);
			event_obj->~Event();
			return -4;//表示操作失败
		}
		setInterval(t);
	}

	// url
	nodes = doc.elementsByTagName("URL");
	if( nodes.isEmpty() )//没有，出错了
	{
		event_obj = new(stack_mem) IOEvent(
			Event::IOEVENT_LOAD_FAILED,			//事件类型：保存失败
			file_name.toStdString().c_str(),	//设备名称：文件名
			0,									//错误类型，可能会过时了
			"cannot find tag " "URL",			//错误描述
			doc.toString().toStdString().c_str()//xml的内容
		);
		eventHappen(*event_obj);
		event_obj->~Event();
		return -5;//表示操作失败
	}
	else
	{
		QDomElement elem = nodes.at(0).toElement();
		setURL( elem.text() );	//这里就没有认证了，如果填的是""也算过了，但是合在后期抓取的时候报错
	}

	// 脚本名/可执行文件名/动态库名
	nodes = doc.elementsByTagName("Parser");
	if( nodes.isEmpty() )//没有，出错了
	{
		event_obj = new(stack_mem) IOEvent(
			Event::IOEVENT_LOAD_FAILED,			//事件类型：保存失败
			file_name.toStdString().c_str(),	//设备名称：文件名
			0,									//错误类型，可能会过时了
			"cannot find tag " "Parser",		//错误描述
			doc.toString().toStdString().c_str()//xml的内容
		);
		eventHappen(*event_obj);
		event_obj->~Event();
		return -6;//表示操作失败
	}
	else
	{
		QDomElement elem = nodes.at(0).toElement();
		setScript( elem.text() );	//这里就没有认证了，如果填的是""也算过了，但是合在后期处理的时候报错
	}

	// 策略类型
	nodes = doc.elementsByTagName("Policy");
	if( nodes.isEmpty() )//没有，出错了
	{
		event_obj = new(stack_mem) IOEvent(
			Event::IOEVENT_LOAD_FAILED,			//事件类型：保存失败
			file_name.toStdString().c_str(),	//设备名称：文件名
			0,									//错误类型，可能会过时了
			"cannot find tag " "Policy",		//错误描述
			doc.toString().toStdString().c_str()//xml的内容
		);
		eventHappen(*event_obj);
		event_obj->~Event();
		return -7;//表示操作失败
	}
	else
	{
		QDomElement elem = nodes.at(0).toElement();
		unsigned int p = elem.text().toUInt(&handle_result);
		if( !handle_result )//不是数字，出错了
		{
			event_obj = new(stack_mem) IOEvent(
				Event::IOEVENT_LOAD_FAILED,			//事件类型：保存失败
				file_name.toStdString().c_str(),	//设备名称：文件名
				0,									//错误类型，可能会过时了
				"tag value error " "Policy",		//错误描述
				elem.text().toStdString().c_str()	//text的内容
			);
			eventHappen(*event_obj);
			event_obj->~Event();
			return -8;//表示操作失败
		}
		setPolicy(p);
	}

	if( _paser_policy==POLICY_TYPE_SHAREDLIB )//载入函数名
	{
		nodes = doc.elementsByTagName("ParseFunc");
		if( nodes.isEmpty() )//没有，出错了
		{
			event_obj = new(stack_mem) IOEvent(
				Event::IOEVENT_LOAD_FAILED,			//事件类型：保存失败
				file_name.toStdString().c_str(),	//设备名称：文件名
				0,									//错误类型，可能会过时了
				"cannot find tag " "ParseFunc",	//错误描述
				doc.toString().toStdString().c_str()//xml的内容
			);
			eventHappen(*event_obj);
			event_obj->~Event();
			return -9;//表示操作失败
		}
		else
		{
			QDomElement elem = nodes.at(0).toElement();
			setSharedLibFunc( elem.text().toStdString().c_str() );	//这里就没有认证了，如果填的是""也算过了，但是合在后期处理的时候报错
		}
	}

	//成功载入
	QString xml = doc.toString();
	event_obj = new(stack_mem) IOEvent(
		Event::IOEVENT_LOAD_FINISH,			//事件类型：保存失败
		file_name.toStdString().c_str(),	//设备名称：文件名
		0,									//错误类型，可能会过时了
		NULL,								//错误描述
		xml.toStdString().c_str()
	);
	eventHappen(*event_obj);
	event_obj->~Event();

	return 0;
}

/**
 * @func(save) 将程序配置参数保存到配置文件中
 * @file_name 配置文件的文件名
 */
int WidgetConfig::save( const QString& file_name )
{
	unsigned char stack_mem[sizeof(IOEvent)];

    QDomDocument doc("WidgetConfig");
    QDomElement root = doc.createElement("WidgetConfig");
    doc.appendChild(root);
    QDomElement tag;

    // 时间间隔
    tag = doc.createElement("TimeGap");
    root.appendChild(tag);
    QDomText t = doc.createTextNode( QString("%1").arg(_grab_timegap) );
    tag.appendChild(t);

    // url
    tag = doc.createElement("URL");
    root.appendChild(tag);
    t = doc.createTextNode( _data_url );
    tag.appendChild(t);

    // 脚本名/可执行文件名/动态库名
    tag = doc.createElement("Parser");
    root.appendChild( tag );
    t = doc.createTextNode( _script_file );
    tag.appendChild(t);

    // 策略类型
    tag = doc.createElement("Policy");
    root.appendChild( tag );
    t = doc.createTextNode( QString("%1").arg(_paser_policy) );
    tag.appendChild(t);

    // 动态库函数，不一定要有
    if( _paser_policy == POLICY_TYPE_SHAREDLIB )
    {
    	tag = doc.createElement( "ParseFunc" );
    	root.appendChild( tag );
    	t = doc.createTextNode( QString(_dll_funcname) );
    	tag.appendChild(t);
    }

    QString xml = doc.toString();
    QFile outf(file_name);
    bool  handle_result;			//保存执行结果，查看是否有底层错误
    IOEvent *event_obj=NULL;			//需要发送的事件
    handle_result = outf.open( QIODevice::WriteOnly|QIODevice::Truncate );
    if( !handle_result )//出现错误，发送事件
    {
    	event_obj = new(stack_mem) IOEvent(
			Event::IOEVENT_SAVE_FAILED,			//事件类型：保存失败
			file_name.toStdString().c_str(),	//设备名称：文件名
			errno,								//错误类型，可能会过时了
			outf.errorString().toStdString().c_str(),//错误描述
			xml.toStdString().c_str()
		);
    	eventHappen(*event_obj);
    	event_obj->~Event();
    	return -1;//表示操作失败
    }
    if( -1 == outf.write( xml.toUtf8() ) )//出现错误
    {
    	event_obj = new(stack_mem) IOEvent(
			Event::IOEVENT_SAVE_FAILED,			//事件类型：保存失败
			file_name.toStdString().c_str(),	//设备名称：文件名
			errno,								//错误类型，可能会过时了
			outf.errorString().toStdString().c_str(),//错误描述
			xml.toStdString().c_str()
		);
    	eventHappen(*event_obj);
    	event_obj->~Event();
    	outf.close();
    	return -2;//表示操作失败
    }
    if( !outf.flush() )//出现异常（不算错误）
    {
    	event_obj = new(stack_mem) IOEvent(
			Event::IOEVENT_SAVE_FAILED,			//事件类型：保存失败
			file_name.toStdString().c_str(),	//设备名称：文件名
			errno,								//错误类型，可能会过时了
			outf.errorString().toStdString().c_str(),//错误描述
			xml.toStdString().c_str()
		);
		eventHappen(*event_obj);
		event_obj->~Event();
		outf.close();
		return 1;//表示操作有异常
    }

    event_obj = new(stack_mem) IOEvent(
		Event::IOEVENT_SAVE_FINISH,			//事件类型：保存失败
		file_name.toStdString().c_str(),	//设备名称：文件名
		0,									//错误类型，可能会过时了
		NULL,								//错误描述
		xml.toStdString().c_str()
	);
	eventHappen(*event_obj);
	event_obj->~Event();
    outf.close();
	return 0;
}

}
